package com.dxg.provider;

import com.dxg.provider.checker.DefaultPostAuthenticationChecks;
import com.dxg.provider.checker.DefaultPreAuthenticationChecks;
import com.dxg.provider.token.SmsAuthenticationToken;
import javax.annotation.Resource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

/**
 * @author dingxigui
 * @version 1.0
 * @date 2021/7/30 10:24
 *  参考
*  {@link DaoAuthenticationProvider}
 *  {@link AbstractUserDetailsAuthenticationProvider}
 */
public abstract class CustomerAbstractAuthenticationProvider implements AuthenticationProvider, InitializingBean,
    MessageSourceAware {

  protected final Log logger = LogFactory.getLog(getClass());

  // ~ Instance fields
  // ================================================================================================

  protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
  //空缓存，不管传什么都返回null
  private UserCache userCache = new NullUserCache();
  private boolean forcePrincipalAsString = false;
  protected boolean hideUserNotFoundExceptions = true;
  private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks(messages);
  private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks(messages);
  //空授权映射，传什么返回
  private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

  // ~ Methods
  // ========================================================================================================

  /**
   *   继承类 可以重写次方法
   * @param userDetails
   * @param authentication
   * @throws AuthenticationException
   */
  protected abstract  void additionalAuthenticationChecks(UserDetails userDetails, Authentication authentication)
      throws AuthenticationException;


  @Override
  public final void afterPropertiesSet() throws Exception {
    Assert.notNull(this.userCache, "A user cache must be set");
    Assert.notNull(this.messages, "A message source must be set");
    doAfterPropertiesSet();
  }

  @Override
  public Authentication authenticate(Authentication authentication)
      throws AuthenticationException {

    // 决定用户名
    String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
        : authentication.getName();

    boolean cacheWasUsed = true;
    //先从缓存中寻找
    UserDetails user = this.userCache.getUserFromCache(username);
    //缓存中不存在，则执行查询
    if (user == null) {
      cacheWasUsed = false;

      try {
        // 获取userDetailService
        user = retrieveUser(username,authentication);
      }
      catch (UsernameNotFoundException notFound) {
        logger.debug("用户名 '" + username + "' 找不到");
        //默认允许隐藏用户名找不到异常，不向前端展示，只显示用户名或密码错误
        if (hideUserNotFoundExceptions) {
          throw new BadCredentialsException(messages.getMessage(
              "AbstractUserDetailsAuthenticationProvider.badCredentials",
              "Bad credentials"));
        }
        else {
          throw notFound;
        }
      }

      Assert.notNull(user,
          "retrieveUser 获取UserDetails 返回为空");
    }

    try {
      //在校验钱做一些检查操作
      preAuthenticationChecks.check(user);
      //实际校验令牌操作留给子类
      additionalAuthenticationChecks(user, authentication);
    }
    catch (AuthenticationException exception) {
      if (cacheWasUsed) {
        //有问题，再次重试一片，确保数据不是来自于缓存
        cacheWasUsed = false;
        user = retrieveUser(username, authentication);
        preAuthenticationChecks.check(user);
        additionalAuthenticationChecks(user, authentication);
      }
      else {
        throw exception;
      }
    }

    postAuthenticationChecks.check(user);
    //没有使用缓存，且校验成功，则将成功结果进行缓存
    if (!cacheWasUsed) {
      this.userCache.putUserInCache(user);
    }

    Object principalToReturn = user;

    if (forcePrincipalAsString) {
      principalToReturn = user.getUsername();
    }

    return createSuccessAuthentication(principalToReturn, authentication, user);
  }

  /**
   *  校验成功后，创建一个成功的token
   *  此方法允许子类覆盖
   * @param principal
   * @param authentication
   * @param user
   * @return
   */
  protected Authentication createSuccessAuthentication(Object principal,
      Authentication authentication, UserDetails user) {

    SmsAuthenticationToken result = new SmsAuthenticationToken(
        principal, authentication.getCredentials(),
        authoritiesMapper.mapAuthorities(user.getAuthorities()));
    //确保原始认证token,details依旧存在（上来还没认证，可能在token放了些额外信息）
    result.setDetails(authentication.getDetails());

    return result;
  }

  protected void doAfterPropertiesSet() throws Exception {
  }

  public UserCache getUserCache() {
    return userCache;
  }

  public boolean isForcePrincipalAsString() {
    return forcePrincipalAsString;
  }

  public boolean isHideUserNotFoundExceptions() {
    return hideUserNotFoundExceptions;
  }

  /**
   *  获取用户
   * @param username
   * @param authentication
   * @return
   * @throws AuthenticationException
   */
  protected abstract UserDetails retrieveUser(String username, Authentication authentication)
      throws AuthenticationException;

  public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
    this.forcePrincipalAsString = forcePrincipalAsString;
  }

  /**
   * 是否隐藏隐藏用户找不到异常，默认true，
   * @param hideUserNotFoundExceptions
   */
  public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
    this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
  }

  @Override
  public void setMessageSource(MessageSource messageSource) {
    this.messages = new MessageSourceAccessor(messageSource);
  }

  public void setUserCache(UserCache userCache) {
    this.userCache = userCache;
  }


  protected UserDetailsChecker getPreAuthenticationChecks() {
    return preAuthenticationChecks;
  }


  public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) {
    this.preAuthenticationChecks = preAuthenticationChecks;
  }

  protected UserDetailsChecker getPostAuthenticationChecks() {
    return postAuthenticationChecks;
  }

  public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) {
    this.postAuthenticationChecks = postAuthenticationChecks;
  }

  public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
    this.authoritiesMapper = authoritiesMapper;
  }



}
